feat(env): add environment validation with tool detection#306
Conversation
Implements enhanced environment validation with tool detection: - Add codeframe/core/environment.py: - ToolDetector base class with version parsing - Ecosystem-specific detectors (Python, JavaScript, Rust, Generic) - ProjectTypeDetector for auto-detecting project type from manifests - EnvironmentValidator with health scoring and recommendations - Version parsing and compatibility checking - Add codeframe/core/installer.py: - ToolInstaller main class delegating to sub-installers - PipInstaller for Python packages (prefers uv over pip) - NpmInstaller for JavaScript packages - CargoInstaller for Rust tools (rustup components and cargo packages) - SystemInstaller for system packages (apt/brew/choco) - Installation history tracking in .codeframe/environment.json - Add codeframe/cli/env_commands.py: - `cf env check` - Quick environment validation with health score - `cf env doctor` - Comprehensive diagnostics with Rich tables - `cf env install-missing <tool>` - Install specific tool - `cf env auto-install` - Install all missing required tools Test coverage: 94 new tests (all passing) - tests/core/test_environment.py (47 tests) - tests/core/test_installer.py (35 tests) - tests/cli/test_env_commands.py (12 tests)
WalkthroughIntroduces a comprehensive environment validation and tool installation system to the CodeFRAME CLI, including commands for checking project health, installing missing tools, comprehensive diagnostics, and batch tool management. The system detects project types, manages tool versions, and provides actionable recommendations. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI as env check<br/>(Command)
participant Validator as EnvironmentValidator
participant Detector as ProjectTypeDetector
participant ToolDetect as ToolDetector<br/>variants
participant Output as Output<br/>(UI)
User->>CLI: Run 'env check --project .'
CLI->>Validator: validate_environment(project_path)
Validator->>Detector: detect_project_type(project_dir)
Detector-->>Validator: project_type
Validator->>ToolDetect: _detect_all_tools(required_tools)
ToolDetect-->>Validator: detected_tools dict
Validator->>Validator: calculate_health_score()
Validator->>Validator: generate_recommendations()
Validator-->>CLI: ValidationResult
CLI->>Output: Display health score,<br/>tools, recommendations
Output-->>User: Exit 0 (healthy)<br/>or 1 (unhealthy)
sequenceDiagram
actor User
participant CLI as env install-missing<br/>(Command)
participant Installer as ToolInstaller
participant SubInstaller as Concrete Installer<br/>(Pip/Npm/etc)
participant Process as subprocess
participant History as history.json
User->>CLI: Run 'env install-missing pylint'
CLI->>Installer: install_tool(tool_name, confirm=True)
Installer->>Installer: _get_installer_for_tool()
Installer->>SubInstaller: can_install(tool_name)
SubInstaller-->>Installer: true/false
Installer->>SubInstaller: get_install_command()
SubInstaller-->>Installer: command_string
CLI->>CLI: Display command,<br/>prompt for confirmation
User->>CLI: Confirm installation
Installer->>SubInstaller: install_tool(confirm=False)
SubInstaller->>Process: subprocess.run(command)
Process-->>SubInstaller: result
SubInstaller->>Installer: InstallResult
Installer->>History: record_installation(result)
Installer-->>CLI: InstallResult
CLI->>User: Display success/failure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Add environment validation with tool detection and expose
|
| def install_tool( | ||
| self, | ||
| tool_name: str, | ||
| confirm: bool = True, |
There was a problem hiding this comment.
🟢 Low
The confirm parameter is declared but never used—installation proceeds unconditionally. Consider either implementing confirmation logic or removing the parameter to avoid misleading callers.
🚀 Want me to fix this? Reply ex: "fix it for me".
| return installer.get_install_command(tool_name) | ||
| return "" | ||
|
|
||
| def verify_installation(self, tool_name: str) -> bool: |
There was a problem hiding this comment.
🟢 Low
Using shutil.which(tool_name) assumes the CLI name equals the package name; many tools differ and Python-only libs have no CLI. Suggest a centralized resolver that maps tool→executable and uses importlib.util.find_spec() for libs, then use it in verify_installation and installers’ skip checks.
🚀 Want me to fix this? Reply ex: "fix it for me".
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@codeframe/core/environment.py`:
- Around line 93-101: The is_healthy_with_threshold method currently only checks
health_score against the threshold while is_healthy also requires no missing
tools; update is_healthy_with_threshold (in class containing health_score and
missing_tools) to mirror is_healthy semantics by returning self.health_score >=
threshold and len(self.missing_tools) == 0 so callers get consistent behavior
with the missing_tools check.
- Around line 158-168: In compare_versions, do not return 0 when
parse_version(version1) or parse_version(version2) is None because that silently
treats unparseable versions as equal; instead change the behavior in
compare_versions to return -1 (or raise a clear ValueError) when either v1 or v2
is None so that check_version_compatibility will fail the >= min_version check;
update the compare_versions function (and any docstring/comments) to document
this defensive behavior and ensure callers handle the error if you choose to
raise.
In `@codeframe/core/installer.py`:
- Around line 121-126: The install_tool method's confirm parameter is documented
but ignored; update install_tool (and the same method in NpmInstaller,
CargoInstaller, SystemInstaller) to actually prompt when confirm is True and
abort when the user declines (unless force is True). Locate the install_tool
implementations and add a short confirmation step (e.g., prompt the user with a
y/n question) that returns an InstallResult indicating cancellation if the user
answers no, ensure that when force is True the prompt is skipped, and keep the
method's existing return semantics and error handling.
- Around line 137-143: The current pre-install check uses
shutil.which(tool_name), which only detects CLI executables and thus misses
importable Python libraries (e.g., httpx, requests); update the logic in the
installer (around the block that references tool_name, force, and returns
InstallResult/InstallStatus) to distinguish CLI tools from Python packages and,
for Python libraries, check importability via importlib (e.g.,
importlib.util.find_spec(package_name) or try
importlib.import_module(package_name)) instead of shutil.which; add or use a
parameter/flag (e.g., package_name or is_python_package/tool_type) so callers
can indicate a library vs executable and ensure the skipped/installed decision
uses the correct check.
In `@tests/cli/test_env_commands.py`:
- Around line 10-13: Remove the unused imports causing F401 in
tests/cli/test_env_commands.py: drop Path from pathlib, and MagicMock and patch
from unittest.mock if they are not referenced in this file; keep only the
imports actually used (e.g., pytest) so unused symbols (Path, MagicMock, patch)
are removed from the import block.
In `@tests/core/test_environment.py`:
- Around line 11-17: Remove the unused imports causing F401 in
tests/core/test_environment.py by editing the top import block to only import
symbols actually referenced in the file; inspect usage of shutil, subprocess,
Path, Optional, MagicMock, patch, call, and pytest, then delete any of those
names that are not used (or collapse them into a single minimal import line).
Ensure imports like MagicMock/patch/call remain only if the test uses
unittest.mock functionality and keep pytest only if used; re-run linting to
confirm F401 is resolved.
In `@tests/core/test_installer.py`:
- Around line 10-15: The file's import block contains unused imports causing
F401 lint failures; remove the unused symbols (e.g., json, subprocess, Path from
pathlib, and unused names from unittest.mock such as MagicMock, patch, call, and
pytest if not referenced elsewhere) from the top-level imports in
tests/core/test_installer.py so only actually used imports remain (locate the
import statement that declares json, subprocess, Path, MagicMock, patch, call,
pytest and delete the unused ones).
🧹 Nitpick comments (4)
codeframe/core/installer.py (3)
38-51: Consider logging when defaulting to linux for unknown platforms.When
platform.system()returns an unexpected value (e.g., "FreeBSD"), silently defaulting to "linux" may cause confusing failures. A debug log would aid troubleshooting.🔧 Suggested improvement
system = platform.system() if system == "Linux": return "linux" elif system == "Darwin": return "darwin" elif system == "Windows": return "windows" + logger.debug(f"Unknown platform '{system}', defaulting to linux") return "linux" # Default to linux
265-272: MissingTimeoutExpiredhandling unlikePipInstaller.
PipInstallerexplicitly catchessubprocess.TimeoutExpired(lines 172-178), butNpmInstallercatches only genericException. This loses timeout-specific context in error messages. The same inconsistency exists inCargoInstaller.♻️ Add explicit timeout handling for consistency
+ except subprocess.TimeoutExpired: + return InstallResult( + tool_name=tool_name, + status=InstallStatus.FAILED, + message=f"Installation of {tool_name} timed out", + command_used=command, + ) except Exception as e: return InstallResult(
537-543: SilentJSONDecodeErrorhandling loses diagnostic context.When the history file is corrupted, silently falling back to an empty dict makes the corruption invisible to debugging. Consider logging a warning.
🔧 Add warning log for corrupted history file
if self.history_file.exists(): try: with open(self.history_file) as f: history = json.load(f) except json.JSONDecodeError: - pass + logger.warning(f"Corrupted history file at {self.history_file}, starting fresh")codeframe/core/environment.py (1)
638-652:_detect_conflictsis a stub with no implementation.The method always returns an empty list. The comment mentions it "could be expanded," but the PR summary advertises conflict detection as a feature. Consider either implementing basic conflict detection (e.g., multiple Python versions, pip vs uv, npm vs yarn) or updating the PR description to note this is a future enhancement.
Would you like me to help implement basic conflict detection logic, such as detecting multiple package managers for the same ecosystem?
| @property | ||
| def is_healthy(self) -> bool: | ||
| """Check if environment is healthy (score >= 0.8).""" | ||
| return self.health_score >= 0.8 and len(self.missing_tools) == 0 | ||
|
|
||
| def is_healthy_with_threshold(self, threshold: float) -> bool: | ||
| """Check health against custom threshold.""" | ||
| return self.health_score >= threshold | ||
|
|
There was a problem hiding this comment.
is_healthy_with_threshold ignores missing_tools unlike is_healthy.
is_healthy (line 96) checks both health_score >= 0.8 and len(self.missing_tools) == 0, but is_healthy_with_threshold only checks the score. This inconsistency may cause unexpected behavior when callers assume the custom threshold method has the same semantics.
🐛 Suggested fix for consistency
def is_healthy_with_threshold(self, threshold: float) -> bool:
"""Check health against custom threshold."""
- return self.health_score >= threshold
+ return self.health_score >= threshold and len(self.missing_tools) == 0🤖 Prompt for AI Agents
In `@codeframe/core/environment.py` around lines 93 - 101, The
is_healthy_with_threshold method currently only checks health_score against the
threshold while is_healthy also requires no missing tools; update
is_healthy_with_threshold (in class containing health_score and missing_tools)
to mirror is_healthy semantics by returning self.health_score >= threshold and
len(self.missing_tools) == 0 so callers get consistent behavior with the
missing_tools check.
| v1 = parse_version(version1) | ||
| v2 = parse_version(version2) | ||
|
|
||
| if v1 is None or v2 is None: | ||
| return 0 # Can't compare, assume equal | ||
|
|
||
| if v1 < v2: | ||
| return -1 | ||
| elif v1 > v2: | ||
| return 1 | ||
| return 0 |
There was a problem hiding this comment.
Returning 0 when version parsing fails may silently pass incompatible versions.
If one version string is unparseable (e.g., malformed output from a tool), compare_versions returns 0, treating them as equal. In check_version_compatibility, this means an unparseable version passes the >= min_version check. Consider returning a value that fails the compatibility check instead, or raising an exception.
🛡️ Suggested defensive approach
if v1 is None or v2 is None:
- return 0 # Can't compare, assume equal
+ # Can't compare - return -1 to fail version checks defensively
+ logger.debug(f"Version comparison failed: {version1!r} vs {version2!r}")
+ return -1📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| v1 = parse_version(version1) | |
| v2 = parse_version(version2) | |
| if v1 is None or v2 is None: | |
| return 0 # Can't compare, assume equal | |
| if v1 < v2: | |
| return -1 | |
| elif v1 > v2: | |
| return 1 | |
| return 0 | |
| if v1 is None or v2 is None: | |
| # Can't compare - return -1 to fail version checks defensively | |
| logger.debug(f"Version comparison failed: {version1!r} vs {version2!r}") | |
| return -1 |
🤖 Prompt for AI Agents
In `@codeframe/core/environment.py` around lines 158 - 168, In compare_versions,
do not return 0 when parse_version(version1) or parse_version(version2) is None
because that silently treats unparseable versions as equal; instead change the
behavior in compare_versions to return -1 (or raise a clear ValueError) when
either v1 or v2 is None so that check_version_compatibility will fail the >=
min_version check; update the compare_versions function (and any
docstring/comments) to document this defensive behavior and ensure callers
handle the error if you choose to raise.
| def install_tool( | ||
| self, | ||
| tool_name: str, | ||
| confirm: bool = True, | ||
| force: bool = False, | ||
| ) -> InstallResult: |
There was a problem hiding this comment.
The confirm parameter is documented but not implemented.
The docstring states "Whether to prompt for confirmation" but the parameter is never used. This pattern repeats across all installers (NpmInstaller, CargoInstaller, SystemInstaller). Either implement the confirmation logic or remove the parameter to avoid misleading callers.
🤖 Prompt for AI Agents
In `@codeframe/core/installer.py` around lines 121 - 126, The install_tool
method's confirm parameter is documented but ignored; update install_tool (and
the same method in NpmInstaller, CargoInstaller, SystemInstaller) to actually
prompt when confirm is True and abort when the user declines (unless force is
True). Locate the install_tool implementations and add a short confirmation step
(e.g., prompt the user with a y/n question) that returns an InstallResult
indicating cancellation if the user answers no, ensure that when force is True
the prompt is skipped, and keep the method's existing return semantics and error
handling.
| # Check if already installed | ||
| if not force and shutil.which(tool_name): | ||
| return InstallResult( | ||
| tool_name=tool_name, | ||
| status=InstallStatus.SKIPPED, | ||
| message=f"{tool_name} is already installed", | ||
| ) |
There was a problem hiding this comment.
shutil.which() won't detect installed Python libraries.
Packages like httpx and requests are importable libraries, not CLI executables. The check on line 138 will always return None for these, causing unnecessary reinstallation attempts.
🐛 Suggested fix: use importlib or distinguish libraries from CLI tools
+import importlib.util
+
+# In PipInstaller class, add method:
+def _is_installed(self, tool_name: str) -> bool:
+ """Check if a Python package is installed."""
+ # First check if it's a CLI tool
+ if shutil.which(tool_name):
+ return True
+ # Then check if it's an importable package
+ spec = importlib.util.find_spec(tool_name.replace("-", "_"))
+ return spec is not None
def install_tool(self, tool_name: str, confirm: bool = True, force: bool = False) -> InstallResult:
# Check if already installed
- if not force and shutil.which(tool_name):
+ if not force and self._is_installed(tool_name):🤖 Prompt for AI Agents
In `@codeframe/core/installer.py` around lines 137 - 143, The current pre-install
check uses shutil.which(tool_name), which only detects CLI executables and thus
misses importable Python libraries (e.g., httpx, requests); update the logic in
the installer (around the block that references tool_name, force, and returns
InstallResult/InstallStatus) to distinguish CLI tools from Python packages and,
for Python libraries, check importability via importlib (e.g.,
importlib.util.find_spec(package_name) or try
importlib.import_module(package_name)) instead of shutil.which; add or use a
parameter/flag (e.g., package_name or is_python_package/tool_type) so callers
can indicate a library vs executable and ensure the skipped/installed decision
uses the correct check.
| from pathlib import Path | ||
| from unittest.mock import MagicMock, patch | ||
|
|
||
| import pytest |
There was a problem hiding this comment.
Remove unused imports to fix F401 lint failures
CI flags unused imports in this block; please drop them to unblock the test suite.
🧹 Proposed fix
-from pathlib import Path
-from unittest.mock import MagicMock, patch
-
-import pytest
+from unittest.mock import patch📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| from unittest.mock import patch |
🧰 Tools
🪛 GitHub Actions: Test Suite (Unit + E2E)
[error] 10-10: F401: Remove unused import: pathlib.Path
[error] 11-11: F401: Remove unused import: unittest.mock.MagicMock
[error] 13-13: F401: Remove unused import: pytest
🤖 Prompt for AI Agents
In `@tests/cli/test_env_commands.py` around lines 10 - 13, Remove the unused
imports causing F401 in tests/cli/test_env_commands.py: drop Path from pathlib,
and MagicMock and patch from unittest.mock if they are not referenced in this
file; keep only the imports actually used (e.g., pytest) so unused symbols
(Path, MagicMock, patch) are removed from the import block.
Code Review SummaryOverall, this is a well-implemented feature that addresses a critical gap in the Golden Path. The code follows established patterns and includes comprehensive test coverage. ✅ Strengths
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@tests/test_new_feature.py`:
- Around line 3-12: The test function test_new_feature is a placeholder that
only asserts True==True; either implement it with real assertions validating the
new environment validation feature (e.g., call the public API/function(s) added
in the PR and assert expected outputs/errors) or remove the function if it's no
longer needed; locate the test_new_feature function in the tests to replace the
placeholder Act/Assert with calls to the new feature's functions/classes and
proper assertions (or delete the test) so it provides actual coverage.
🧹 Nitpick comments (2)
tests/core/test_installer.py (1)
31-47: Consider adding a test for unknown platform default.The tests cover Linux, macOS, and Windows, but per the PR comments,
get_platform()defaults to"linux"for unknown platforms. Consider adding a test to verify this behavior:def test_get_platform_unknown_defaults_to_linux(self): """Test that unknown platform defaults to linux.""" with patch("platform.system", return_value="FreeBSD"): assert get_platform() == "linux"tests/cli/test_env_commands.py (1)
1-23: Consider addingpytestmark = pytest.mark.v2for CLI tests.Per coding guidelines, CLI tests should be marked with v2. While the guideline specifically mentions "CLI tests using
codeframe.cli.app", these tests exerciseenv_appwhich is a sub-application of the main CLI. Consider adding the module-level marker:"""Tests for environment CLI commands. ... """ +import pytest from unittest.mock import patch from typer.testing import CliRunner ... + +pytestmark = pytest.mark.v2As per coding guidelines: "Mark new v2 tests with
pytest.mark.v2decorator or module-levelpytestmark = pytest.mark.v2for CLI tests usingcodeframe.cli.app"
1. CLI Wireframe Alignment - Add env subcommand documentation to CLI_WIREFRAME.md - Document check, doctor, install-missing, auto-install commands 2. Safer Command Splitting - Replace .split() with explicit list construction in installers - Add _get_install_cmd_parts() methods for safe subprocess args 3. Document Version Parsing Limitations - Add explicit supported/unsupported version format documentation - Note limitations for build metadata and pre-release ordering 4. Platform Detection Warning - Log warning when defaulting to 'linux' for unknown platforms 5. Test Fixes - Update integration tests to properly mock shutil.which for uv checks
| pass | ||
|
|
||
| # Add new entry |
There was a problem hiding this comment.
🟡 Medium
If the history file contains valid JSON without an "installations" key, line 588 raises a KeyError. Consider using history.setdefault("installations", {}) before accessing it.
| pass | |
| # Add new entry | |
| pass | |
| history.setdefault("installations", {}) |
🚀 Want me to fix this? Reply ex: "fix it for me".
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@codeframe/core/installer.py`:
- Around line 349-364: CargoInstaller.install_tool currently proceeds without
verifying whether the requested tool is already installed, causing inconsistent
behavior with NpmInstaller; update CargoInstaller.install_tool to perform the
same pre-install check as NpmInstaller by calling the existing installer check
method (e.g., self.is_installed(tool_name) or the class's installed-tools
lookup) and: if the tool is present and force is False, return the appropriate
InstallResult (skipped/already_installed) without running install steps; if
confirm is required keep the existing confirmation flow; only continue to run
the install routine when the tool is not installed or force is True.
- Around line 250-267: NpmInstaller.install_tool currently always runs the
installer even when force is False; add a pre-install existence check similar to
PipInstaller by using shutil.which() (or equivalent) to detect if the tool
binary is already available when global_install is True and force is False, and
if found return an InstallResult indicating skipped/already-installed instead of
performing npm install; update NpmInstaller.install_tool to check
shutil.which(tool_name) (or the resolved binary name) and respect the force and
global_install flags before invoking the install logic.
🧹 Nitpick comments (8)
codeframe/core/installer.py (3)
193-200: Consider catching more specific exceptions.The bare
except Exceptionis overly broad and may mask unexpected errors. Consider catching specific exceptions likeFileNotFoundErrororPermissionErrorseparately, or at minimum re-raiseKeyboardInterruptandSystemExit.♻️ Proposed refinement
- except Exception as e: + except (FileNotFoundError, PermissionError, OSError) as e: return InstallResult( tool_name=tool_name, status=InstallStatus.FAILED, message=f"Installation error: {e}", command_used=command, error_output=str(e), )
564-568: Recording SKIPPED results in history may be misleading.
result.successisTruefor bothSUCCESSandSKIPPEDstatuses. Recording skipped installations (tool already present) in history with a timestamp could mislead users into thinking an installation occurred. Consider recording onlySUCCESSstatus.♻️ Suggested change
# Record in history - if result.success: + if result.status == InstallStatus.SUCCESS: self.record_installation(result)
580-586: Silent JSONDecodeError swallows corrupted history without warning.If the history file is corrupted, the error is silently ignored and an empty history is used. Consider logging a warning so users know their history was lost.
🛡️ Suggested improvement
if self.history_file.exists(): try: with open(self.history_file) as f: history = json.load(f) except json.JSONDecodeError: - pass + logger.warning(f"Corrupted history file {self.history_file}, starting fresh")tests/core/test_installer.py (2)
311-318: Test assertion is broader than the current implementation.The assertion checks for
apt,dnf, oryum, but theSystemInstallerimplementation only outputsaptfor Linux. This isn't incorrect, but the test may pass if the implementation accidentally changes to a different package manager.Consider tightening the assertion
def test_get_install_command_linux(self): """Test install command on Linux.""" installer = SystemInstaller() with patch("codeframe.core.installer.get_platform", return_value="linux"): cmd = installer.get_install_command("git") - assert "apt" in cmd or "dnf" in cmd or "yum" in cmd + assert "apt" in cmd # Current implementation uses apt for Linux assert "git" in cmd
327-334: Similar assertion broadness for Windows.The assertion checks for
chocoorwinget, but implementation only useschoco. Consider aligning the test with the actual implementation.codeframe/core/environment.py (3)
407-423: First-match detection may miss polyglot projects.Projects with multiple manifest files (e.g.,
pyproject.toml+package.json) will only be detected as the first-matched type. This is acceptable for v1, but consider documenting this limitation or returning a list of detected types in the future.
643-658:_detect_conflictsis a placeholder returning empty list.The method is documented as detecting tool conflicts but currently returns an empty list. Consider either implementing real conflict detection (e.g., multiple Python versions, conflicting formatters) or adding a TODO comment explaining the intended behavior.
Would you like me to suggest implementation ideas for common tool conflicts (e.g., black vs. yapf formatter conflicts, different Python version requirements)?
659-697: Install commands partially duplicateinstaller.pylogic.The
_get_install_commandmethod has hardcoded install commands that overlap withPipInstaller,NpmInstaller, etc. Consider delegating to the installer module to keep commands in sync.♻️ Suggested approach
+from codeframe.core.installer import ToolInstaller + +class EnvironmentValidator: + def __init__(self): + # ... existing code ... + self._tool_installer = ToolInstaller() + def _get_install_command(self, tool_name: str, project_type: str) -> str: - # Hardcoded commands... + # Delegate to installer for supported tools + cmd = self._tool_installer.get_install_command(tool_name) + if cmd: + return cmd + + # Fallback for tools not in installer + return f"Install {tool_name} using your system package manager"
…aller - NpmInstaller.install_tool: Add shutil.which() check for global installs when force=False, returning SKIPPED if tool already exists - CargoInstaller.install_tool: Add pre-install check using binary name mapping (ripgrep->rg, fd-find->fd, clippy->cargo-clippy, cargo-edit->cargo-add) - Skip binary check for rust-src (has no binary, it's source code) - Add comprehensive tests for new pre-install behavior - Remove placeholder tests/test_new_feature.py This ensures consistent behavior across all installers: PipInstaller, NpmInstaller, and CargoInstaller now all check if tools are already installed before proceeding with installation.
| result = installer.install_tool(tool_name, confirm=confirm, force=force) | ||
|
|
||
| # Record in history | ||
| if result.success: |
There was a problem hiding this comment.
🟢 Low
When a tool is skipped (already installed), result.success is still True, causing record_installation to overwrite the original timestamp/command with None. Consider checking for InstallStatus.SUCCESS instead of result.success.
| if result.success: | |
| if result.status == InstallStatus.SUCCESS: |
🚀 Want me to fix this? Reply ex: "fix it for me".
Summary
Changes
New Core Modules
codeframe/core/environment.py- Tool detection and validation engine:ToolDetectorbase class withshutil.which()detection and version parsingPythonToolDetector,JavaScriptToolDetector,RustToolDetector,GenericToolDetectorProjectTypeDetector- auto-detects project type from manifest files (pyproject.toml, package.json, Cargo.toml, go.mod)EnvironmentValidator- validates environment with health scoring and recommendationscodeframe/core/installer.py- Platform-specific tool installation:ToolInstallermain class delegating to sub-installersPipInstaller- Python packages (prefers uv over pip)NpmInstaller- JavaScript packages (global or local)CargoInstaller- Rust tools (rustup components and cargo packages)SystemInstaller- System packages (apt/brew/choco based on platform).codeframe/environment.jsonNew CLI Commands
codeframe/cli/env_commands.py:cf env check- Quick environment validation with health scorecf env doctor- Comprehensive diagnostics with Rich tablescf env install-missing <tool>- Install specific missing toolcf env auto-install- Install all missing required toolsTest plan
tests/core/test_environment.py)tests/core/test_installer.py)tests/cli/test_env_commands.py)cf env checkshows 100% health scorecf env doctordisplays tool table with versionsSummary by CodeRabbit
New Features
envCLI command group for environment validation and tool managementenv check: validates project environment and displays health statusenv doctor: provides comprehensive diagnostics with tool detection and recommendationsenv install-missing: installs specified missing tools interactivelyenv auto-install: automatically installs all missing tools with progress trackingDocumentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.